/*
 * This file is part of aKoustiK.

 * aKoustiK is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * aKoustiK is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with aKoustiK.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <KGlobal>
#include <KSharedConfig>
#include <KSharedConfigPtr>

#include <QtCore/QDebug>
#include <QtCore/QTime>
#include <QtGui/QMessageBox>

#include <kore.h>
#include <player.h>

Player::Player(QObject *parent) : QObject(parent), currentIndex(-1) {
    qDebug("Player: constructing...");
    playlist = Kore::instance().playlist();
    mAudioOutput = new Phonon::AudioOutput(Phonon::MusicCategory, this);
    mMediaObject = new Phonon::MediaObject(this);
    mMediaObject->setTickInterval(1000);
    Phonon::createPath(mMediaObject, mAudioOutput);

    mShuffle = KGlobal::config()->group("Player").readEntry("shuffle", true);
    mRepeat = KGlobal::config()->group("Player").readEntry("repeat", true);
    mVolume = KGlobal::config()->group("Player").readEntry("volume", 50);
    mAudioOutput->setVolume(mVolume);

    connect(mAudioOutput, SIGNAL(volumeChanged(qreal)), SLOT(outputVolumeChanged(qreal)));
    connect(mMediaObject, SIGNAL(seekableChanged(bool)), SLOT(sourceSeekableChanged(bool)));
    connect(mMediaObject, SIGNAL(totalTimeChanged(qint64)), SLOT(sourceTotalTimeChanged(qint64)));
    connect(mMediaObject, SIGNAL(tick(qint64)), SLOT(sourceTick(qint64)));
    connect(mMediaObject, SIGNAL(currentSourceChanged(Phonon::MediaSource)), SLOT(sourceSourceChanged(Phonon::MediaSource)));
    connect(mMediaObject, SIGNAL(stateChanged(Phonon::State,Phonon::State)), SLOT(sourceStateChanged(Phonon::State,Phonon::State)));
    connect(mMediaObject, SIGNAL(aboutToFinish()), SLOT(aboutToFinish()));
    connect(playlist, SIGNAL(removingItem(int,Item*)), SLOT(playlistItemRemoving(int,Item*)));

    // init random:
    qsrand(QTime::currentTime().msec());
}

Player::~Player() {
}

void Player::previous() {
    qDebug("Player: previous");

    if (historyList.isEmpty()) {
        qDebug("Player: history is empty, moving to next item...");
        next();
        return;
    }

    Item* item = historyTakeLast();
    currentItem = *item;

    int oldIndex = currentIndex;
    currentIndex = playlist->indexOf(item);
    emit currentItemChanged(currentIndex, oldIndex);
    play();
}

void Player::play() {
    qDebug("Player: play");

    // first of all we'll check for current item:
    if (!currentItem.isValid()) {
        /*
         moveToNext() will return false if we have nothing to play
         and it will generate next best item for us
         */
        if (!moveToNext()) {
            return;
        }
    }

    // set source if it's different:
    if (currentItem != mMediaObject->currentSource().url()) {
        qDebug("Player: setting another source...");
        mMediaObject->setCurrentSource(currentItem.url());
        mMediaObject->play();
        return;
    }

    // if we are playing already - move to beginning
    if (mMediaObject->state() == Phonon::PlayingState) {
        qDebug("Player: current state is Playing, moving to start...");
        mMediaObject->seek(0);
        return;
    }

    // otherwise - just start playback
    qDebug("Player: invoke playback...");
    mMediaObject->play();
    return;
}

void Player::pause() {
    qDebug("Player: pause");
    mMediaObject->pause();
}

void Player::stop() {
    qDebug("Player: stop");
    mMediaObject->stop();
}

void Player::next() {
    qDebug("Player: next");
    if (moveToNext()) {
        play();
    }
    else {
        stop();
    }
}

void Player::mute(bool mute) {
    if (mute) {
        mMute = true;
        mAudioOutput->setMuted(true);
    }
    else {
        mMute = false;
        mAudioOutput->setMuted(false);
        mAudioOutput->setVolume(mVolume);
    }
}

void Player::play(int index) {
    qDebug("Player: play(%i)...", index);
    if (index < 0) {
        // TODO maybe just do next?
        return;
    }

    int oldIndex = currentIndex;
    currentIndex = index;
    emit currentItemChanged(currentIndex, oldIndex);
    currentItem = *playlist->at(index);
    play();
}

void Player::shuffle(bool shuffle) {
    qDebug() << "Player: shuffle(" << shuffle << ')';
    mShuffle = shuffle;
    KGlobal::config()->group("Player").writeEntry("shuffle", shuffle);
}

void Player::repeat(bool repeat) {
    qDebug() << "Player: repeat(" << repeat << ')';
    mRepeat = repeat;
    KGlobal::config()->group("Player").writeEntry("repeat", repeat);
}

int Player::volume() {
    return mVolume;
}

void Player::setVolume(int volume) {
    if (volume == mVolume) {
        return;
    }
    qDebug() << "Player: volume(" << volume << ')';
    mVolume = volume;
    mAudioOutput->setVolume(static_cast<qreal>(volume) * 0.01);
    KGlobal::config()->group("Player").writeEntry("volume", mVolume);
}

void Player::outputVolumeChanged(qreal value) {
    int v = value * 100;
    if (v == mVolume) {
        return;
    }
    mVolume = v;
    emit volumeChanged(v);
}

void Player::sourceSeekableChanged(bool isSeekable) {
    emit seekableChanged(isSeekable);
}

void Player::sourceTick(qint64 time) {
    emit tick(time);
}

void Player::sourceTotalTimeChanged(qint64 newTotalTime) {
    emit totalTimeChanged(newTotalTime);
}

qint64 Player::totalTime() {
    return mMediaObject->totalTime();
}

qint64 Player::time() {
    return mMediaObject->currentTime();
}

void Player::seek(qint64 time) {
    mMediaObject->seek(time);
}

void Player::sourceSourceChanged(const Phonon::MediaSource &source) {
    Q_UNUSED(source);
    emit sourceChanged();
}

void Player::sourceStateChanged(Phonon::State newstate, Phonon::State oldstate) {
    Q_UNUSED(oldstate);
    if (newstate == Phonon::ErrorState) {
        // TODO add error type check
        // TODO use KDE dialog instead
        QMessageBox::critical(0, "Critical Error", mMediaObject->errorString(), QMessageBox::Close);
    }
}

void Player::aboutToFinish() {
    if (moveToNext()) {
        if (currentItem.isValid()) {
            mMediaObject->enqueue(currentItem.url());
        }
    }
}

bool Player::moveToNext() {

    // saving current item to history:
    if (currentItem.isValid()) {
        // taking the same item but directly from playlist:
        Item* item = playlist->at(currentIndex);
        historyAppend(item);
    }

    // save oldIndex to emit signal later:
    int oldIndex = currentIndex;

    /*
     If playlist is emtpy - we should return false.
     Procedure is different between shuffle modes, but the same between repeat modes.
     The only thing different in repeat - it is possible we should stop after current track.
     */
    if (!mRepeat) {
        // on shuffle we should compare greySet size with playlist size
        // otherwise - compare current index with size of playlist
        if (playlist->isEmpty() || (mShuffle && !greySet.isEmpty() && greySet.size() == playlist->size()) || (!mShuffle && currentIndex == playlist->size() - 1)) {
            currentItem = Item();
            currentIndex = -1;
            // clean greySet before stopping:
            greySet.clear();
            emit currentItemChanged(currentIndex, oldIndex);
            return false;
        }
    }

    // if we are still here - we should move to next item:
    // on shuffle just generate next random and play it:
    if (mShuffle) {
        currentIndex = generateRandom();
        Item* i = playlist->at(currentIndex);
        currentItem = *i;
    }
    // non-shuffle - increment current index and play it:
    else {
        currentIndex++;
        // we already checked non-repeat mode, so it's repeat and we should move to first item:
        if (currentIndex == playlist->size()) {
            currentIndex = 0;
        }
        currentItem = *(playlist->at(currentIndex));
    }

    emit currentItemChanged(currentIndex, oldIndex);
    return true;
}

int Player::generateRandom() {
    // taken from http://wiki.forum.nokia.com/index.php/CS001350_-_Generating_random-value_integers_in_Qt:
    int rand;
    Item* item;
    do {
        rand = qrand() % (playlist->size());
        item = playlist->at(rand);
    } while (rand == currentIndex || greySet.contains(item));
    qDebug("PlayingQueue: random: %i", rand);
    return rand;
}

int Player::current() const {
    return currentIndex;
}

void Player::playlistItemRemoving(int itemIndex, Item *item) {
    Q_UNUSED(item);
    // maybe we should shift current index up?
    if (itemIndex < currentIndex) {
        currentIndex--;
    }
    // we should reset current item if it was deleted, so it will not be added to history:
    // TODO check what else will fail if we will delete current item
    if (currentItem == *item) {
        currentItem = Item();
        currentIndex = -1;
    }
    historyRemove(item);
}

bool Player::shuffle() {
    return mShuffle;
}

bool Player::repeat() {
    return mRepeat;
}

bool Player::isGrey(int index) const {
    if (mShuffle) {
        return greySet.contains(playlist->at(index));
    }
    else {
        return index < currentIndex;
    }
}

Item* Player::historyTakeLast() {
    Item* item = historyList.takeLast();
    greySet.remove(item);
    return item;
}

void Player::historyAppend(Item *item) {
    historyList.append(item);
    greySet.insert(item);
    historyCleanup();
}

void Player::historyRemove(Item *item) {
    historyList.removeAll(item);
    greySet.remove(item);
}

void Player::historyCleanup() {
    if (greySet.isEmpty()) {
        return;
    }
    int totalSize = playlist->size();
    if (totalSize < 1) {
        return;
    }

    int greySize;
    if (mRepeat) {
        greySize = totalSize / 2;
    }
    else {
        greySize = totalSize;
    }

    // how many items we should remove from greySet:
    int diff = greySet.size() - greySize;
    if (diff <= 0) {
        return;
    }

    /*
     History must always be bigger than greySet, so no additional checks.
     We will iterate over the history to remove oldest items from greySet.

     But we must not delete last halfSize, so iterating up to historyList.size() - halfSize

     We already know how much items we exceeded,
     so we should start iteration from historyList.size() - halfSize - diff
     */
    int historyIndex = historyList.size() - greySize;
    for (int i = historyIndex - diff; i < historyIndex; i++) {
        Item* item = historyList.at(i);
        greySet.remove(item);
    }
}
